package ph.com.gs3.formalistics.service.managers;

import java.util.ArrayList;
import java.util.List;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import ph.com.gs3.formalistics.global.utilities.logging.FLLogger;
import ph.com.gs3.formalistics.model.communicators.CommentCommunicatorModule;
import ph.com.gs3.formalistics.model.communicators.HttpCommunicatorModule.CommunicatorException;
import ph.com.gs3.formalistics.model.dao.CommentsDAO;
import ph.com.gs3.formalistics.model.dao.UserAccountsDAO;
import ph.com.gs3.formalistics.model.dao.SQLiteDataAccessObject.DeleteFailedException;
import ph.com.gs3.formalistics.model.dao.SQLiteDataAccessObject.UpdateFailedException;
import ph.com.gs3.formalistics.model.dao.UserAccountsDAO.IncompleteUserValuesException;
import ph.com.gs3.formalistics.model.valueobjects.application.APIResponse;
import ph.com.gs3.formalistics.model.valueobjects.application.APIResponse.APIInvalidResponseException;
import ph.com.gs3.formalistics.model.valueobjects.business.Comment;
import ph.com.gs3.formalistics.model.valueobjects.business.User;
import ph.com.gs3.formalistics.view.valueobjects.parsers.TextHtmlParser;

public class CommentManager {

	public static final String TAG = CommentManager.class.getSimpleName();

	private UserAccountsDAO userAccountsDAO;
	private CommentsDAO commentsDAO;
	private CommentCommunicatorModule commentCommunicatorModule;

	private List<String> downloadedUserIdList;

	public CommentManager(UserAccountsDAO userAccountsDAO, CommentsDAO commentsDAO,
	        CommentCommunicatorModule commentCommunicatorModule) {

		this.userAccountsDAO = userAccountsDAO;
		this.commentsDAO = commentsDAO;
		this.commentCommunicatorModule = commentCommunicatorModule;

	}

	/**
	 * Downloads comments that are created after the last update date specified and the
	 * list of the documents/requests passed to this method. The list of document id must
	 * belong to a single form, downloading comments from multiple documents that also
	 * belong to multipe forms is not yet supported by the API. If the comment's author is
	 * not yet existing in this device's local database, it will automatically be saved.
	 * 
	 * @param formId
	 *            The web id of the form of the list of documents.
	 * @param documentIdList
	 *            The list of web id of the documents which comments will be updated.
	 * @param lastUpdateDate
	 *            The date when the comments are last updated
	 * @param rangeFrom
	 *            Will fetch comments starting from this parameter.
	 * @param rangeTo
	 *            Will fetch comments up to this parameter.
	 * @return The list of comments successfully downloaded from the server.
	 * @throws DownloadCommentsFailedException
	 */
	public List<Comment> downloadNewCommentsAndUsers(String formId, String[] documentIdList,
	        String lastUpdateDate, int rangeFrom, int rangeTo)
	        throws DownloadCommentsFailedException {

		List<Comment> comments = new ArrayList<>();

		APIResponse response;
		String server = commentCommunicatorModule.getServer();

		try {
			response = commentCommunicatorModule.getFormDocumentsCommentUpdates(formId,
			        documentIdList, lastUpdateDate, rangeFrom, rangeTo);
		} catch (CommunicatorException | APIInvalidResponseException e) {
			throw new DownloadCommentsFailedException("Failed to download comments from form: "
			        + formId, e);
		}

		if (response.isOperationSuccessful()) {

			// cache all the available users for this user, this will be used for checking
			// if a new user should be saved based on the comment's author
			userAccountsDAO.open();
			downloadedUserIdList = userAccountsDAO.getAvailableUserIdListByServer(server);

			// cache the data of the current user, common data from the comment authors
			// will be based from this user
			User currentUser = userAccountsDAO.getActiveUserAccount();
			userAccountsDAO.close();

			String rawResultString = response.getResults();
			try {
				JSONArray rawResultsJSONArray = new JSONArray(rawResultString);

				int commentCount = rawResultsJSONArray.length();
				for (int i = 0; i < commentCount; i++) {
					Comment comment = Comment.createFromJSON(rawResultsJSONArray.getJSONObject(i),
					        server);

					// save the user information if he is not yet existing

					if (!downloadedUserIdList.contains(comment.getAuthorWebId())) {

						FLLogger.d(
						        TAG,
						        "User "
						                + comment.getAuthorDisplayName()
						                + " is not yet existing in this device's database, his/her data will be saved.");

						// TODO: test this later
						User commentAuthor = extractAuthorFromComment(currentUser, comment);
						userAccountsDAO.open();
						try {
							userAccountsDAO.saveOrUpdateUser(commentAuthor);
						} catch (IncompleteUserValuesException e) {
							FLLogger.w(
							        TAG,
							        "Warning, unable to save author of comment "
							                + comment.getWebId() + " " + e.getMessage());
						} finally {
							userAccountsDAO.close();
						}

					}

					// save the new comment to the database

					String webId = comment.getWebId();
					Comment savedComment;

					try {
						commentsDAO.open();

						if (commentsDAO.isCommentExisting(server, webId)) {

							FLLogger.w(
							        TAG,
							        "Comment is already existing here but was included in the new comments from the server. Check the API for issues.");

							try {
								savedComment = commentsDAO.updateComment(webId, server, comment);
							} catch (UpdateFailedException e) {
								e.printStackTrace();
								FLLogger.e(TAG, "Failed to update comment: " + e.getMessage());
								continue;
							}
						} else {
							savedComment = commentsDAO.saveComment(comment);
						}
					} finally {
						commentsDAO.close();
					}

					// add the new comment to the results
					if (savedComment != null) {
						comments.add(savedComment);
					} else {
						// TODO: throw save failed exception here
						FLLogger.e(TAG, "Failed to save comment: " + comment.getText());
					}

				}

			} catch (JSONException e) {
				// todo throw parse failed exception here
				e.printStackTrace();
				return null;
			}

		}

		return comments;

	}

	public Comment submitOutgoingComment(Comment comment) throws SubmitCommentFailedException,
	        CommentSaveFailedException {

		// parse the comment text to html
		comment.setText(TextHtmlParser.stringToHTML(comment.getText()));

		// submit the comment to the server
		APIResponse response;
		try {
			response = commentCommunicatorModule.submitComment(comment);
		} catch (CommunicatorException | APIInvalidResponseException e) {
			throw new SubmitCommentFailedException(e);
		}

		FLLogger.d(TAG, "" + response.getResults());

		if (!response.isOperationSuccessful()) {
			throw new SubmitCommentFailedException(response.getErrorMessage());
		}

		String commentDbId = comment.getDbId();

		JSONObject resultingCommentValues;
		String resultingCommentWebId;
		String resultingCommentDateCreated;

		try {
			resultingCommentValues = new JSONObject(response.getResults());
			resultingCommentWebId = resultingCommentValues.getString("id");
			resultingCommentDateCreated = resultingCommentValues.getString("date_created");
		} catch (JSONException e) {
			throw new SubmitCommentFailedException("Unable to interpret response from server. ", e);
		}

		Comment submittedComment = null;

		try {
			// save the submitted comment to the local device database
			commentsDAO.open();
			submittedComment = commentsDAO.updateOutgoingDocument(commentDbId,
			        resultingCommentWebId, resultingCommentDateCreated);
		} catch (UpdateFailedException e) {
			throw new CommentSaveFailedException(e);
		} finally {
			commentsDAO.close();
		}

		return submittedComment;

	}

	public void deleteCommentsDeletedFromServer() {

		String server = commentCommunicatorModule.getServer();

		commentsDAO.open();
		String lastUpdateDate = commentsDAO.getDeletedCommentsLastUpdateDate(server);
		commentsDAO.close();

		if (lastUpdateDate == null) {
			FLLogger.d(TAG, "Skipping synch of comment deletion, no last update yet.");
			return;
		}

		APIResponse response;
		try {
			response = commentCommunicatorModule.getDeletedComments(lastUpdateDate);
		} catch (CommunicatorException | APIInvalidResponseException e) {
			// TODO log or throw failed fetching of deleted comments
			e.printStackTrace();
			return;
		}

		if (response.isOperationSuccessful()) {
			String rawResultString = response.getResults();

			commentsDAO.open();

			JSONArray rawResultsJSONArray;
			try {
				rawResultsJSONArray = new JSONArray(rawResultString);
				int commentCount = rawResultsJSONArray.length();
				for (int i = 0; i < commentCount; i++) {
					Comment comment = Comment.createFromJSON(rawResultsJSONArray.getJSONObject(i),
					        server);

					try {
						FLLogger.d(TAG, "Deleting comment " + comment.getWebId());
						commentsDAO.deleteComment(comment.getServer(), comment.getWebId());
						FLLogger.d(TAG, "Deleted comment " + comment.getWebId());
					} catch (DeleteFailedException e) {
						FLLogger.w(TAG, "Failed to delete comment " + comment.getWebId()
						        + ", it's probably already deleted here.");
					}

				}
			} catch (JSONException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

			commentsDAO.close();

		} else {
			// TODO: throw an excpetion here
			FLLogger.e(TAG,
			        "Failed to download comments from the server: " + response.getErrorMessage());
		}

	}

	public void deleteCommentMarkedForDeletion(Comment comment)
	        throws CommentDeleteFromServerFailedException {

		// delete the comment from the server
		APIResponse response;
		try {
			response = commentCommunicatorModule.deleteComment(comment.getWebId());
		} catch (CommunicatorException | APIInvalidResponseException e) {
			throw new CommentDeleteFromServerFailedException(e);
		}

		// permanently delete the comment in the database
		if (response.isOperationSuccessful()) {
			commentsDAO.open();
			try {
				commentsDAO.deleteComment(comment.getDbId());
			} catch (DeleteFailedException e) {
				// TODO Add logging capabilities here
				e.printStackTrace();
			} finally {
				commentsDAO.close();
			}

		} else {
			throw new CommentDeleteFromServerFailedException(response.getErrorMessage());
		}

	}

	private User extractAuthorFromComment(User currentUser, Comment comment) {

		User author = new User();

		author.setAccountServer(currentUser.getAccountServer());
		author.setCompanyId(currentUser.getCompanyId());
		author.setCompanyName(currentUser.getCompanyName());

		author.setWebId(Integer.parseInt(comment.getAuthorWebId()));
		author.setEmail(comment.getAuthorEmail());
		author.setDisplayName(comment.getAuthorDisplayName());
		author.setImageURL(comment.getAuthorImageURL());

		return author;

	}

	// =================================================================================
	// {{ Exceptions

	public static class DownloadCommentsFailedException extends Exception {

		private static final long serialVersionUID = 5982218912275260041L;

		public DownloadCommentsFailedException(String message) {
			super(message);
		}

		public DownloadCommentsFailedException(Throwable throwable) {
			super(throwable);
		}

		public DownloadCommentsFailedException(String message, Throwable throwable) {
			super(message, throwable);
		}
	}

	public static class SubmitCommentFailedException extends Exception {

		private static final long serialVersionUID = 6076000350347890654L;

		public SubmitCommentFailedException(String message) {
			super(message);
		}

		public SubmitCommentFailedException(Throwable throwable) {
			super(throwable);
		}

		public SubmitCommentFailedException(String message, Throwable throwable) {
			super(message, throwable);
		}

	}

	public static class CommentSaveFailedException extends Exception {

		private static final long serialVersionUID = -797465648466741478L;

		public CommentSaveFailedException(String message) {
			super(message);
		}

		public CommentSaveFailedException(Throwable throwable) {
			super(throwable);
		}

		public CommentSaveFailedException(String message, Throwable throwable) {
			super(message, throwable);
		}

	}

	public static class CommentDeleteFromServerFailedException extends Exception {

		private static final long serialVersionUID = 6660687021428398671L;

		public CommentDeleteFromServerFailedException(String message) {
			super(message);
		}

		public CommentDeleteFromServerFailedException(Throwable throwable) {
			super(throwable);
		}

		public CommentDeleteFromServerFailedException(String message, Throwable throwable) {
			super(message, throwable);
		}

	}

	// }}

}
